#!/usr/bin/perl


## Creator: Richie Crews
## Version: 1.0

## Changes:
##  1.0: Complete overhaul.


## Modules
use strict;
use vars qw($configFile %config %options @recordings);
use Getopt::Std; # Used for command line options
use DBI; # Used for SQLite functions
use Time::Format qw(%strftime); # Used for time functions
use Net::Ping; # Used for ping functions
use XML::Simple; # Use for parsing the XML of the TiVo download
use LWP::UserAgent; # Used for creating web connections to TiVo
use HTTP::Request; # Used for downloading from the TiVo
use Net::Twitter; # Used to provide updates via Twitter for new recordings
use WWW::Shorten::TinyURL; # Used to shrink the URL for the TV Episode info 
use Mail::Send; # Used to send email to end-user
## End of Modules

## Variables
## This should be the only think you have to change!
$configFile = "/Users/rcrews/bin/tivo/config";
## End of Variables

## Main
# Parse the options provided by the end-user
getopts('n:t:dc:', \%options);
# Print help if no TiVo host
&printHelp unless $options{t};
# Print help if asked to.
&printHelp if $options{h};
# Read the configuration first
&parseConfig();

# TiVo only option
if ($options{t} && !$options{d} && !$options{c}) {
    if ($ENV{DEBUG}) { print "DEBUG: Checking TiVo for data ONLY\n"; }
    &grabTiVoXmlAndProcess($options{t});
    # Since we validated with the XML download this is a valid TiVo unit
    # check to see if the SQLite database exists
    if (! -e "$config{DBDIR}/$options{t}.db") { &createTiVoDatabase($options{t}); }
    # Now that the database is confirmed, process the recordings and insert
    # or remove the recordings from the database.
    my $recordingsToRemove; 
    my $recordingsToInsert;
    ($recordingsToInsert,$recordingsToRemove) = &processTiVoRecordings($options{t});
    # With the processed recordings completed we can remove/insert into the database as needed
    if (scalar(@$recordingsToRemove) > 0 ) {
        if ($ENV{DEBUG}) { foreach (@$recordingsToRemove) { print "DEBUG: Remove - $_\n"; } }
        &removeEpisodeFromDatabase($options{t},@$recordingsToRemove);
    }
    if (scalar(@$recordingsToInsert) > 0 ) {
        if ($ENV{DEBUG}) { foreach (@$recordingsToInsert) { print "DEBUG: Insert - $_\n"; } }
        &insertEpisodeInDatabase($options{t},@$recordingsToInsert);
        # Now we need to send of notifications if asked
        if ($options{n} =~ /email/i) { &notifyUserViaEmail($options{t},@$recordingsToInsert); }
        if ($options{n} =~ /twitter/i) { &notifyUserViaTwitter($options{t},@$recordingsToInsert); }
    }
}
# Download recordings from TiVo
if ($options{t} && $options{d} && !$options{c}) {
    if ($ENV{DEBUG}) { print "DEBUG: Downloading TiVo recordings for $options{t}\n"; }
    # First Check for Locks
    my $lockCheck = &checkLock($options{t});
    if ($lockCheck == 0) {
        &createLock($options{t});
        my $showsToEncode = &checkForDownloads($options{t}); 
        &removeLock($options{t});
        if (($config{ENCODEDB} == 1) && (scalar(@$showsToEncode) > 0)) {
            if (! -e "$config{DBDIR}/encode.db") {
                &createEncodeDatabase;
            }
            &insertIntoEncodeDatabase(@$showsToEncode); 
        }
    }
}

## End of Main

## Subfunctions

sub checkLock {
    my $database = shift @_;
    my $dbfile = "$config{DBDIR}/$database.db";
    my $currentTimeInEpoch = time;
    if ($ENV{DEBUG}) { print "DEBUG: Current EPOCH time $currentTimeInEpoch\n"; }
    if ($ENV{DEBUG}) { print "DEBUG: Checking for Locks in $dbfile\n"; }
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access database file $dbfile for checking locks : $DBI::errstr\n"; 
    my $count = $dbh->selectall_arrayref("SELECT count(*) FROM locks");
    if (@$count[0] == 0) { return; }
    if ($ENV{DEBUG}) { print "DEBUG: Lock detected in $dbfile\n"; }
    my $res = $dbh->selectall_arrayref("SELECT * FROM locks");
    $dbh->disconnect;
    foreach my $row (@$res) {
        my $lockTimestamp = @$row[0];
        if ($ENV{DEBUG}) { print "DEBUG: Lock time in database is $lockTimestamp\n"; }
        my $timeDiff = $currentTimeInEpoch - $lockTimestamp;
        if ($timeDiff > 86400) {
            # 24 hours
            print "Lock file over 24 hours old detected!\n";
            exit 1;
        }
        else { return 1; }
    }
}

sub createLock {
    my $database = shift @_;
    my $dbfile = "$config{DBDIR}/$database.db";
    if ($ENV{DEBUG}) { print "DEBUG: Creating Locks in $dbfile\n"; }
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access database file $dbfile for locking : $DBI::errstr\n"; 
    my $timestamp = time;
    $dbh->do("INSERT INTO locks (timestamp) VALUES ('$timestamp')") or die "Couldn't insert lock timestamp into $dbfile : $DBI::errstr\n";
    $dbh->disconnect;
    return;
}

sub removeLock {
    my $database = shift @_;
    my $dbfile = "$config{DBDIR}/$database.db";
    if ($ENV{DEBUG}) { print "DEBUG: Removing Locks in $dbfile\n"; }
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access database file $dbfile for lock removal : $DBI::errstr\n"; 
    $dbh->do("DELETE FROM locks") or die "Couldn't remove lock timestamp from $dbfile : $DBI::errstr\n";
    $dbh->disconnect;
    return;
}

sub parseConfig {
    open(CONFIG,"$configFile") or die "Could not access $configFile: $!\n";
    while(<CONFIG>) {
        chomp;
        # Ignore comments
        next if /^#/;
        # Split variable and value
        my ($variable,$value) = split(/=/,$_,2);
        if ($ENV{DEBUG}) { print "DEBUG: Variable: $variable Value: $value\n"; }
        # Insert it into the hash
        $config{$variable} = $value;
    }
    close CONFIG;
    return;
}

# Creates the TiVo databases if not detected.
sub createTiVoDatabase {
    my $host = shift @_;
    my $dbfile = "$config{DBDIR}/$host.db";
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access $host database file $dbfile : $DBI::errstr\n"; 
    # Create the table for the recordings
    $dbh->do("CREATE TABLE recordings (timestamp TEXT, show_name VARCHAR(500), show_title VARCHAR(500), show_desc VARCHAR(1000), channel_num INTEGER, channel_name VARCHAR(50), recording_size INTEGER, hd INTEGER, show_id VARCHAR(30), download_url VARCHAR(500), downloaded INTEGER)") or die "Couldn't create recordings table for $host : $DBI::errstr\n"; 
    $dbh->do("CREATE TABLE locks (timestamp TEXT)") or die "Couldn't create locks table for $host : $DBI::errstr\n";
    return;
}

# Creates the Encode databases if not detected.
sub createEncodeDatabase {
    my $dbfile = "$config{DBDIR}/encode.db";
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access database file $dbfile : $DBI::errstr\n"; 
    # Create the table for the recordings
    $dbh->do("CREATE TABLE recordings (show_name VARCHAR(500), show_title VARCHAR(500), show_desc VARCHAR(1000), hd INTEGER, file_name VARCHAR(500), encoded INTEGER)") or die "Couldn't create recordings table for encode database : $DBI::errstr\n"; 
    $dbh->do("CREATE TABLE locks (timestamp TEXT)") or die "Couldn't create locks table for encode database : $DBI::errstr\n";
    return;
}

sub grabTiVoXmlAndProcess {
    my $tivo = shift @_;
    # First check to see if the TiVo is valid.
    my $xml = new XML::Simple;
    my $ua = LWP::UserAgent->new() or die "Failed to create a LWP::UserAgent!\n";
    $ua->credentials("$tivo:443",'TiVo DVR', 'tivo', $config{MAK});
    if ($ENV{DEBUG}) { print "DEBUG: URL: https://$tivo$config{URL}\n"; }
    my $response = $ua->get("https://$tivo$config{URL}");
    $response = $ua->request(HTTP::Request->new(GET => "https://$tivo$config{URL}"));
    die "XML fetch failed for $tivo.\n" unless $response->is_success();
    # Read TiVo XML file, and break at Item
    my $data = $xml->XMLin($response->content(), ForceArray => ['Item']);
    die "No content to parse.\n" unless defined($data->{Item});
    foreach my $item (@{$data->{Item}}) {
        # Due to the DUMB 128 item limit we have to scan sub folders...
        if ($item->{Details}->{ContentType} =~ /folder/) {
            # Skip the HD folder or you'll get double recordings. Doh!
            if ($item->{Details}->{Title} =~ /^HD Recordings$/) { next; }
            # Skip WishList folders or we will get double recordings reported.
            if ($item->{Links}->{CustomIcon}->{Url} =~ /wishlist-folder/) { next; }
            if ($item->{Details}->{TotalItems} > 0) { 
               &ScanTiVoFolder($tivo,$item->{Details}->{UniqueId});
            }
        }
        else {
            my $title = $item->{Details}->{Title};
            my $episodetitle = $item->{Details}->{EpisodeTitle};
            my $description = $item->{Details}->{Description};
            my $hexdate = $item->{Details}->{CaptureDate};
            my $channel = $item->{Details}->{SourceChannel};
            my $channelname = $item->{Details}->{SourceStation};
            my $rawsize = $item->{Details}->{SourceSize};
            my $hd = $item->{Details}->{HighDefinition};
            my $url = $item->{Links}->{Content}->{Url};
            my $recordstatus = $item->{Details}->{InProgress};
            my $programid = $item->{Details}->{ProgramId};
            # If the programid (episode number) is less than 10 add two zeros after EP.
            if ($ENV{DEBUG}) { print "DEBUG: Original ProgramID - $programid\n"; }
            if ($ENV{DEBUG}) { print "DEBUG: ProgramID Size - " . length($programid) . "\n"; }
            if (length($programid) < 13) { $programid =~ s/EP(\d+)/EP00\1/; }
            if ($ENV{DEBUG}) { print "DEBUG: New ProgramID - $programid\n"; }
            # Convert the date to epoch from hex, then epoch to YYYY-MM-DD HH:MM:SS Format
            $hexdate =~ s/0x//;
            my $epochdate = hex($hexdate);
            my $date =  "$strftime{'%Y-%m-%d %H:%M:%S', $epochdate}";
            # If the recording is still occuring skip it for one that is completed
            if ($recordstatus =~ /yes/i) { next; }
            # No Episode ID = no show
            if ($programid =~ /^$/) { next; }
            # Sometimes there is no episode title..
            if ($episodetitle =~ /^$/) { $episodetitle = "NONE"; }
            # Sometimes there is no description..
            if ($description =~ /^$/) { $description = "NONE"; }
            if ($ENV{DEBUG}) { print "DEBUG: Found Show $title - $episodetitle\n"; }
            if ($ENV{DEBUG}) { print "DEBUG: Record Date $date\n"; }
            push(@recordings,"$date|$title|$episodetitle|$description|$channel|$channelname|$rawsize|$hd|$programid|$url");
        }
    }
    return;
}

sub ScanTiVoFolder {
    my $tivo = shift @_;
    my $uniqueId = shift @_;
    # First check to see if the TiVo is valid.
    if ($ENV{DEBUG}) { print "DEBUG: Processing folder ID $uniqueId on $tivo\n"; } 
    my $URL = "/TiVoConnect?Command=QueryContainer&Container=/NowPlaying/$uniqueId";
    my $xml = new XML::Simple;
    my $ua = LWP::UserAgent->new() or die "Failed to create a LWP::UserAgent!\n";
    $ua->credentials("$tivo:443",'TiVo DVR', 'tivo', $config{MAK});
    if ($ENV{DEBUG}) { print "DEBUG: URL: https://$tivo$URL\n"; }
    my $response = $ua->get("https://$tivo$URL");
    $response = $ua->request(HTTP::Request->new(GET => "https://$tivo$URL"));
    die "XML fetch failed for $tivo.\n" unless $response->is_success();
    # Read TiVo XML file, and break at Item
    my $data = $xml->XMLin($response->content(), ForceArray => ['Item']);
    die "No content to parse.\n" unless defined($data->{Item});
    foreach my $item (@{$data->{Item}}) {
            my $title = $item->{Details}->{Title};
            my $episodetitle = $item->{Details}->{EpisodeTitle};
            my $description = $item->{Details}->{Description};
            my $hexdate = $item->{Details}->{CaptureDate};
            my $channel = $item->{Details}->{SourceChannel};
            my $channelname = $item->{Details}->{SourceStation};
            my $rawsize = $item->{Details}->{SourceSize};
            my $hd = $item->{Details}->{HighDefinition};
            my $url = $item->{Links}->{Content}->{Url};
            my $recordstatus = $item->{Details}->{InProgress};
            my $programid = $item->{Details}->{ProgramId};
            # If the programid (episode number) is less than 10 add two zeros after EP.
            if ($ENV{DEBUG}) { print "DEBUG: Original ProgramID - $programid\n"; }
            if ($ENV{DEBUG}) { print "DEBUG: ProgramID Size - " . length($programid) . "\n"; }
            if (length($programid) < 13) { $programid =~ s/EP(\d+)/EP00\1/; }
            if ($ENV{DEBUG}) { print "DEBUG: New ProgramID - $programid\n"; }
            # Convert the date to epoch from hex, then epoch to YYYY-MM-DD HH:MM:SS Format
            $hexdate =~ s/0x//;
            my $epochdate = hex($hexdate);
            my $date =  "$strftime{'%Y-%m-%d %H:%M:%S', $epochdate}";
            # If the recording is still occuring skip it for one that is completed
            if ($recordstatus =~ /yes/i) { next; }
            # No Episode ID = no show
            if ($programid =~ /^$/) { next; }
            # Sometimes there is no episode title..
            if ($episodetitle =~ /^$/) { $episodetitle = "NONE"; }
            # Sometimes there is no description..
            if ($description =~ /^$/) { $description = "NONE"; }
            if ($ENV{DEBUG}) { print "DEBUG: Found Show $title - $episodetitle\n"; }
            if ($ENV{DEBUG}) { print "DEBUG: Record Date $date\n"; }
            push(@recordings,"$date|$title|$episodetitle|$description|$channel|$channelname|$rawsize|$hd|$programid|$url");
    }
}

sub processTiVoRecordings {
    my $tivo = shift @_;
    # Setup the arrays
    my @insertRecordings = ();
    my @removeRecordings = ();
    # First query the current TiVo database and capture the data into an array
    my $databaseQuery = "SELECT * FROM recordings";
    my $databaseData = &queryDatabase($tivo,$databaseQuery);
    if ($ENV{DEBUG}) { print "DEBUG: Current TiVo Database record count: " . scalar(@$databaseData) . "\n"; }
    if (scalar(@$databaseData) == 0) {
        # No records, this is an empty database.
        # Assign the current recordings array to the returned array and return.
        @insertRecordings = @recordings;
        return (\@insertRecordings, \@removeRecordings);
    }    
    if (scalar(@$databaseData) > 0) {
        # Records found, compare the old records with the new recordings pulled from the TiVo
        # show_id is the best match for this since those are unique numbers, if something is missing
        # then remove it from the database by pushing it into the remove array
        my @currentShowIDs = ();
        foreach my $dbLine (@$databaseData) { 
            my ($show_id) = (split(/\|/,$dbLine))[8];
            push(@currentShowIDs,$show_id);
        }
        my @foundShowIDs = ();
        foreach my $dbLine (@recordings) { 
            my ($show_id) = (split(/\|/,$dbLine))[8];
            push(@foundShowIDs,$show_id);
        }
        # Now compare the two arrays to determine what has been removed.
        my @removeIDs = do {
                                my %seen; @seen{ @foundShowIDs } = ( 1 ) x @foundShowIDs;
                                grep !$seen{ $_ }, @currentShowIDs;
                               };
        if (scalar(@removeIDs) > 0) {
            if ($ENV{DEBUG}) { foreach (@removeIDs) { print "DEBUG: Remove ID $_\n"; } }
            foreach my $ID (@removeIDs) {
                my ($removeLine) = grep(/$ID/,@$databaseData);
                push(@removeRecordings,$removeLine);
            }
        }
        # Now compare the two arrays the OTHER way to determine what has been added. 
        my @newIDs = do {
                     my %seen; @seen{ @currentShowIDs } = ( 1 ) x @currentShowIDs;
                     grep !$seen{ $_ }, @foundShowIDs;
                     };
        if (scalar(@newIDs) > 0) {
            if ($ENV{DEBUG}) { foreach (@newIDs) { print "DEBUG: Insert ID $_\n"; } }
            foreach my $ID (@newIDs) {
                my ($insertLine) = grep(/$ID/,@recordings);
                push(@insertRecordings,$insertLine);
            }
        }
        return (\@insertRecordings, \@removeRecordings);
    }
}

sub checkForDownloads {
    my $tivo = shift @_;
    # Open the autograb file from the configuration file
    open(AUTOGRAB,$config{AUTOGRABCONFIG}) or die "Couldn't open autograb configuration $config{AUTOGRABCONFIG} for reading : $!\n";
    # Grab all the non downloaded shows
    my $databaseQuery = "SELECT * FROM recordings WHERE downloaded = 0";
    my $notDownloadedData = &queryDatabase($tivo,$databaseQuery);
    my @autoGrabShows = ();
    # Parse through the autograb configuration
    while(<AUTOGRAB>) {
        chomp;
        next if $_ =~ /^#/; # Ignore comments
        my ($show,$downloadTiVo) = split(/\|/,$_);
        # Ignore if the TiVo in the configuration isn't the TiVo we were given to check
        next unless $downloadTiVo =~ /$tivo/;
        if ($ENV{DEBUG}) { print "DEBUG: TV Show [$show] setup for autograb on [$tivo]\n"; }
        # Push the shows into an array  
        push(@autoGrabShows,$show);
    }
    close AUTOGRAB;
    my @showsToEncode = ();
    # Now loop through the shows and download the recordings if any found
    foreach my $show (@autoGrabShows) {
        if ($ENV{DEBUG}) { print "DEBUG: Checking for new downloads of [$show]\n"; }
        # Pull all episodes to download for the specific show
        my @showsToDownload = grep(/\|$show\|/,@$notDownloadedData);
        if ($ENV{DEBUG}) { foreach(@showsToDownload) { print "DEBUG: Download - $_\n"; } }
        # Pass the array of the episodes for a specific show to be downloaded.
        my @finishedShows = ();
        @finishedShows = &downloadEpisodes($tivo,@showsToDownload);
        if (scalar(@finishedShows) > 0 ) {
            foreach my $encode (@finishedShows) { push(@showsToEncode,$encode); }
        }
    }
    return \@showsToEncode;
}

sub downloadEpisodes {
    my $tivo = shift @_;
    my @recordingsToDownload = @_;
    my @successfulDownloads = ();
    # First determine if the download directory even exists
    if (! -d $config{TIVOSTORAGE}) { 
            mkdir $config{TIVOSTORAGE};
            print "TiVo Download directory $config{TIVOSTORAGE} was missing, it has been created\n";
    }
    # Now process the data and start the download
    my @finishedShows = ();
    foreach my $line (@recordingsToDownload) {
        chomp $line;
        my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = split(/\|/,$line);
        # Setup the LWP agent for the download process
        my $ua = LWP::UserAgent->new() or die "Failed to create a LWP::UserAgent for [$tivo] to download [$show_name] - [$show_title]\n";
        $ua->credentials("$tivo:443",'TiVo DVR', 'tivo', "$config{MAK}");
        $ua->credentials("$tivo:80",'TiVo DVR', 'tivo', "$config{MAK}");
        $ua->cookie_jar( {} );
        # Since we are saving the file to the filesystem, we need to clean up the name a bit to make it more readible.
        # Change timestamp to YYYYMMDD format
        $timestamp =~ s/(\d+)-(\d+)-(\d+).*/$1$2$3/;
        # Replace Spaces with Underscores
        $show_name =~ s/\s/_/g;
        # Replace Spaces and/or Slashes with Underscores
        $show_title =~ s/\s/_/g;
        $show_title =~ s/\//_/g;
        $download_url .= "&Format=video/x-tivo-mpeg";
        my $file_name = "$config{TIVOSTORAGE}/$timestamp-$show_name-$show_title.tivo";
        if ($ENV{DEBUG}) { print "DEBUG: Download File name - $file_name\n"; }
        if ($ENV{DEBUG}) { print "DEBUG: Download URL - $download_url\n"; }
        my $response = $ua->get($download_url,":content_file" => $file_name);
        # Check to see if the file was actually downloaded.
        if (! -e $file_name) {
           print "Couldn't download [$show_name] - [$show_title] to file $file_name!\n";
           exit 1;
        }
        # Check to see if the file size is correct to what TiVo has for it.
        ## BUG: TiVo records the file size but it always doesn't match the download size
        ## The recording is still valid so commenting out this code
        #my $downloadedFileSize = -s $file_name;
        #if ($downloadedFileSize != $recording_size) {
        #   print "Downloaded file $file_name has size mismatch, expected $recording_size but received $downloadedFileSize.\n";
        #   unlink $file_name;
        #   exit 1;
        #}
        # If we made it this far the file down was a success
        # If the configuration has auto decrypt turned on it would be now to run it.
        if ($config{AUTODECRYPT} == 1) {
            # Decrypt the recording.
            &decryptTiVoEpisode($file_name);
            # If the user has configured the .tivo files to be removed after decoding, perform the task
            if ($config{REMOVETIVO} == 1) { unlink $file_name; }
        }
        # Now we need to tell the database this download is completed.
        &setDownloadCompleted($tivo,$line);
        my $line = "$show_name|$show_title|$show_desc|$hd|$file_name";
        if ($ENV{DEBUG}) { print "DEBUG: Setting up [$show_name] - [$show_title] to encode\n"; } 
        push(@finishedShows,$line);
    }
    return \@finishedShows;
}

sub setDownloadCompleted {
    my $tivo = shift @_;
    my $dataLine = shift @_;
    my $dbfile = "$config{DBDIR}/$tivo.db";
    # Open the DB connection
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") or die "Couldn't access $tivo database file $dbfile for reading : $DBI::errstr\n";
    my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = split(/\|/,$dataLine);
    if ($ENV{DEBUG}) { print "DEBUG: Marking [$show_name] - [$show_title] as downloaded\n"; }
    $dbh->do("UPDATE recordings SET downloaded = 1 WHERE show_id = '$show_id'") or "Couldn't update record in $dbfile : $DBI::errstr\n";
    $dbh->disconnect();
    return;
}

sub queryDatabase {
    my $tivo = shift @_;
    my $query = shift @_;
    my $dbfile = "$config{DBDIR}/$tivo.db";
    # Open the DB connection
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","") or die "Couldn't access $tivo database file $dbfile for reading : $DBI::errstr\n";
    # Grab all the recordings for this TiVo
    my $res = $dbh->selectall_arrayref($query);
    my @currentData = ();
    foreach my $row (@$res) { 
        my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = @$row;
        push(@currentData,"$timestamp|$show_name|$show_title|$show_desc|$channel_num|$channel_name|$recording_size|$hd|$show_id|$download_url|$downloaded");
    }
    # Close the DB Connection       
    $dbh->disconnect;
    return \@currentData;
}

sub insertEpisodeInDatabase {
    my $tivo = shift @_;
    my $dbfile = "$config{DBDIR}/$tivo.db";
    my @recordingsToInsert = @_;
    # Open DB Connection
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access $tivo database file $dbfile for writing : $DBI::errstr\n";
    foreach my $line (@recordingsToInsert) {
        my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = split(/\|/,$line);
        # These four items MAY contain quotes escape them incase, everything else should be safe
        my $q_show_name = $dbh->quote($show_name);
        my $q_show_title = $dbh->quote($show_title);
        my $q_show_desc = $dbh->quote($show_desc);
        my $q_download_url = $dbh->quote($download_url);
        $dbh->do("INSERT INTO recordings (timestamp,show_name,show_title,show_desc,channel_num,channel_name,recording_size,hd,show_id,download_url,downloaded) VALUES ('$timestamp',$q_show_name,$q_show_title,$q_show_desc,$channel_num,'$channel_name','$recording_size','$hd','$show_id',$q_download_url,0)") or die "Couldn't insert record into $dbfile : $DBI::errstr\n";
    }
    # Close DB Connection
    $dbh->disconnect;
    return;
}

sub insertIntoEncodeDatabase {
    my $dbfile = "$config{DBDIR}/encode.db";
    my @recordingsToInsert = @_;
    # Open DB Connection
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access encode database file $dbfile for writing : $DBI::errstr\n";
    foreach my $line (@recordingsToInsert) {
        my ($show_name,$show_title,$show_desc,$hd,$encoded) = split(/\|/,$line);
        # These four items MAY contain quotes escape them incase, everything else should be safe
        my $q_show_name = $dbh->quote($show_name);
        my $q_show_title = $dbh->quote($show_title);
        my $q_show_desc = $dbh->quote($show_desc);
        $dbh->do("INSERT INTO recordings (show_name,show_title,show_desc,hd,encoded) VALUES ($q_show_name,$q_show_title,$q_show_desc,'$hd',0)") or die "Couldn't insert record into $dbfile : $DBI::errstr\n";
    }
    # Close DB Connection
    $dbh->disconnect;
    return;
}

sub removeEpisodeFromDatabase {
    my $tivo = shift @_;
    my $dbfile = "$config{DBDIR}/$tivo.db";
    my @recordingsToRemove = @_;
    # Open DB Connection
    my $dbh = DBI->connect("dbi:SQLite:dbname=$dbfile","","",{RaiseError => 1, AutoCommit => 1}) or die "Couldn't access $tivo database file $dbfile for writing : $DBI::errstr\n";
    foreach my $line (@recordingsToRemove) {
        my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = split(/\|/,$line);
        # These four items MAY contain quotes escape them incase, everything else should be safe
        my $q_show_name = $dbh->quote($show_name);
        my $q_show_title = $dbh->quote($show_title);
        my $q_show_desc = $dbh->quote($show_desc);
        my $q_download_url = $dbh->quote($download_url);
        $dbh->do("DELETE FROM recordings WHERE show_id = '$show_id'") or die "Couldn't delete record from $dbfile : $DBI::errstr\n";
    }
    # Close DB Connection
    $dbh->disconnect;
    return;
}

sub decryptTiVoEpisode {
    my $tivoFile = shift @_;
    if ($ENV{DEBUG}) { print "DEBUG: Decrypting $tivoFile\n"; }
    ( my $outputFile = $tivoFile ) =~ s/\.tivo$/.mpeg/;
    # Setting up for future versions on other platforms?
    my $decodeCommand = "$config{TIVODECODE} -m $config{MAK} -o \"$outputFile\" \"$tivoFile\" 1>/dev/null";
    # Determine if the tivodecode binary is there
    die "Missing tivodecode binary $config{TIVODECODE}.\n" unless -e $config{TIVODECODE};
    # Run the decode.
    system($decodeCommand);
    # If it fails, report it and stop;
    if ($? == -1) {
        print "Failed to decode TiVo file $tivoFile into $outputFile using the following command:\n";
        print "$decodeCommand\n";
        exit 1;
    }
    return;
}

sub processTiVoEpisode {

}

sub notifyUserViaEmail {
    if ($ENV{DEBUG}) { print "DEBUG: Received request to notify via Email\n"; }
    my $tivo = shift @_;
    my @recordingsToEmail = @_;
    my $msg = Mail::Send->new(Subject => "New TiVo Recordings on $tivo", To => "$config{EMAIL}");
    my $fh = $msg->open;
    print $fh "The following Items were recently recorded on the TiVo:\n\n";
    foreach my $line (@recordingsToEmail) {
        chomp $line;
        my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = split(/\|/,$line);
        if ($ENV{DEBUG}) { print "DEBUG: Emailing about [$show_name] - [$show_title]\n"; }
        print $fh "$show_name - $show_title\n";
        print $fh "Recording Date/Time: $timestamp\n";
        print $fh "Channel Number: $channel_num\n";
        print $fh "Channel Name: $channel_name\n";
        print $fh "Recording Size: " . sprintf("%.3f",((($recording_size / 1024) / 1024) / 1024)) . "GB\n";
        print $fh "HD Recording: " . (($hd == 1) ? "No" : "Yes") . "\n";
        print $fh "Show Description: $show_desc\n";
        print $fh "\n";
    }
    $fh->close or die "Couldn't close E-Mail handler : $!\n";
    return;
}

sub notifyUserViaTwitter {
    if ($ENV{DEBUG}) { print "DEBUG: Received request to notify via Twitter\n"; }
    my $tivo = shift @_;
    my @recordingsToTweet = @_;
    # Setup the Twitter Auth
    my $nt = Net::Twitter->new(
                            traits          => ['API::REST', 'OAuth'],
                            consumer_key    => "$config{TKEY}",
                            consumer_secret => "$config{TSECRET}",
                              );
    # You'll save the token and secret in cookie, config file or session database
    if ($config{TTOKEN} !~ /^$/) { $nt->access_token($config{TTOKEN}); }
    if ($config{TTOKENSECRET} !~ /^$/) { $nt->access_token_secret($config{TTOKENSECRET}); }
    unless ( $nt->authorized ) {
        print "Authorize this app at ", $nt->get_authorization_url, " and enter the PIN#\n";
        my $pin = <STDIN>; # wait for input
        chomp $pin;
        my($access_token, $access_token_secret, $user_id, $screen_name) = $nt->request_access_token(verifier => $pin);
        if ($ENV{DEBUG}) { print "DEBUG: Access Token - $access_token\n"; }
        if ($ENV{DEBUG}) { print "DEBUG: Access Token Secret - $access_token_secret\n"; }
        if (!$config{TTOKEN}) { &storeTwitterTokens("TTOKEN",$access_token); }
        if (!$config{TTOKENSECRET}) { &storeTwitterTokens("TTOKENSECRET",$access_token_secret); }
    }
    foreach my $line (@recordingsToTweet) {
        chomp $line;
        my ($timestamp,$show_name,$show_title,$show_desc,$channel_num,$channel_name,$recording_size,$hd,$show_id,$download_url,$downloaded) = split(/\|/,$line);
        if ($ENV{DEBUG}) { print "DEBUG: Tweeting about [$show_name] - [$show_title]\n"; }
        my $short_url = makeashorterlink("http://tvlistings.zap2it.com/tv/x/$show_id");
        my $result = $nt->update("[$tivo] $show_name: \"$show_title\" recorded at $timestamp on $channel_num $channel_name $short_url");
    }
    return;
}

sub storeTwitterTokens {
    my $configValue = shift @_;
    my $store = shift @_;
    if ($ENV{DEBUG}) { print "DEBUG: Request to store $store in $configValue\n"; }
    # Open the configuration file for WRITING
    open(CONFIGWRITE,">>$configFile");
    print CONFIGWRITE "# Autoadded value for Twitter Posting\n";
    print CONFIGWRITE "$configValue=$store\n";
    close CONFIGWRITE;
    return;
}

sub printHelp {
    print "\n";
    print "USAGE: $0 [-h] [-t <TIVO HOST OR IP>] [-n <NOTIFY METHODS>] [-d] [-c <CONVERT SCRIPT>]\n";
    print "\t-h: This help.\n";
    print "\t-t: The IP or DNS name of the TiVo to monitor.\n";
    print "\t-n: The method to notify by, the following are valid: email and/or twitter\n";
    print "\t\tWhen using more than one method, ensure they are comma seperated\n";
    print "\t\tExample: email,twitter\n";
    print "\t-d: Download recordings specified in autograb.conf\n";
    print "\t-c: The Full path to the conversion script, the following options are passed to the script in order listed below:\n";
    print "\t\tFull path to the decrypted TiVo episode\n";
    print "\t\tHD Recording (Yes/No, required for de-interlacing)\n";
    print "\t\tEpisode Name\n";
    print "\t\tEpisode Title\n";
    print "\t\tEpisode Description\n";
    print "\n";
    exit;
}

## End of Subfunctions

